home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / xulrunner / chrome / toolkit.jar / content / global / viewPartialSource.js < prev    next >
Encoding:
JavaScript  |  2005-07-27  |  16.5 KB  |  479 lines

  1. //@line 40 "/c/mozilla/toolkit/components/viewsource/content/viewPartialSource.js"
  2.  
  3. var gDebug = 0;
  4. var gLineCount = 0;
  5. var gStartTargetLine = 0;
  6. var gEndTargetLine = 0;
  7. var gTargetNode = null;
  8.  
  9. var gEntityConverter = null;
  10. var gWrapLongLines = false;
  11. const gViewSourceCSS = 'resource://gre/res/viewsource.css';
  12. const NS_XHTML = 'http://www.w3.org/1999/xhtml';
  13.  
  14. // These are markers used to delimit the selection during processing. They
  15. // are removed from the final rendering, but we pick space-like characters for
  16. // safety (and futhermore, these are known to be mapped to a 0-length string
  17. // in transliterate.properties). It is okay to set start=end, we use findNext()
  18. // U+200B ZERO WIDTH SPACE
  19. const MARK_SELECTION_START = '\u200B\u200B\u200B\u200B\u200B';
  20. const MARK_SELECTION_END = '\u200B\u200B\u200B\u200B\u200B';
  21.  
  22. function onLoadViewPartialSource()
  23. {
  24.   // check the view_source.wrap_long_lines pref and set the menuitem's checked attribute accordingly
  25.   if (gPrefs) {
  26.     try {
  27.       var wraplonglinesPrefValue = gPrefs.getBoolPref('view_source.wrap_long_lines');
  28.       if (wraplonglinesPrefValue) {
  29.         document.getElementById('menu_wrapLongLines').setAttribute('checked', 'true');
  30.         gWrapLongLines = true;
  31.       }
  32.     } catch (e) { }
  33.     try {
  34.       document.getElementById("menu_highlightSyntax").setAttribute("checked", gPrefs.getBoolPref("view_source.syntax_highlight"));
  35.     } catch (e) {
  36.     }
  37.   } else {
  38.     document.getElementById("menu_highlightSyntax").setAttribute("hidden", "true");
  39.   }
  40.   
  41.   initFindBar();
  42.  
  43.   if (window.arguments[3] == 'selection')
  44.     viewPartialSourceForSelection(window.arguments[2]);
  45.   else
  46.     viewPartialSourceForFragment(window.arguments[2], window.arguments[3]);
  47.  
  48.   window._content.focus();
  49. }
  50.  
  51. function onUnloadViewPartialSource()
  52. {
  53.   uninitFindBar();
  54. }
  55.  
  56. ////////////////////////////////////////////////////////////////////////////////
  57. // view-source of a selection with the special effect of remapping the selection
  58. // to the underlying view-source output
  59. function viewPartialSourceForSelection(selection)
  60. {
  61.   var range = selection.getRangeAt(0);
  62.   var ancestorContainer = range.commonAncestorContainer;
  63.   var doc = ancestorContainer.ownerDocument;
  64.  
  65.   var startContainer = range.startContainer;
  66.   var endContainer = range.endContainer;
  67.   var startOffset = range.startOffset;
  68.   var endOffset = range.endOffset;
  69.  
  70.   // let the ancestor be an element
  71.   if (ancestorContainer.nodeType == Node.TEXT_NODE ||
  72.       ancestorContainer.nodeType == Node.CDATA_SECTION_NODE)
  73.     ancestorContainer = ancestorContainer.parentNode;
  74.  
  75.   // for selectAll, let's use the entire document, including <html>...</html>
  76.   // @see DocumentViewerImpl::SelectAll() for how selectAll is implemented
  77.   try {
  78.     if (ancestorContainer == doc.body)
  79.       ancestorContainer = doc.documentElement;
  80.   } catch (e) { }
  81.  
  82.   // each path is a "child sequence" (a.k.a. "tumbler") that
  83.   // descends from the ancestor down to the boundary point
  84.   var startPath = getPath(ancestorContainer, startContainer);
  85.   var endPath = getPath(ancestorContainer, endContainer);
  86.  
  87.   // clone the fragment of interest and reset everything to be relative to it
  88.   // note: it is with the clone that we operate/munge from now on
  89.   ancestorContainer = ancestorContainer.cloneNode(true);
  90.   startContainer = ancestorContainer;
  91.   endContainer = ancestorContainer;
  92.  
  93.   // Only bother with the selection if it can be remapped. Don't mess with
  94.   // leaf elements (such as <isindex>) that secretly use anynomous content
  95.   // for their display appearance.
  96.   var canDrawSelection = ancestorContainer.hasChildNodes();
  97.   if (canDrawSelection) {
  98.     var i;
  99.     for (i = startPath ? startPath.length-1 : -1; i >= 0; i--) {
  100.       startContainer = startContainer.childNodes.item(startPath[i]);
  101.     }
  102.     for (i = endPath ? endPath.length-1 : -1; i >= 0; i--) {
  103.       endContainer = endContainer.childNodes.item(endPath[i]);
  104.     }
  105.  
  106.     // add special markers to record the extent of the selection
  107.     // note: |startOffset| and |endOffset| are interpreted either as
  108.     // offsets in the text data or as child indices (see the Range spec)
  109.     // (here, munging the end point first to keep the start point safe...)
  110.     var tmpNode;
  111.     if (endContainer.nodeType == Node.TEXT_NODE ||
  112.         endContainer.nodeType == Node.CDATA_SECTION_NODE) {
  113.       // do some extra tweaks to try to avoid the view-source output to look like
  114.       // ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection).
  115.       // To get a neat output, the idea here is to remap the end point from:
  116.       // 1. ...<tag>]...   to   ...]<tag>...
  117.       // 2. ...]</tag>...  to   ...</tag>]...
  118.       if ((endOffset > 0 && endOffset < endContainer.data.length) ||
  119.           !endContainer.parentNode || !endContainer.parentNode.parentNode)
  120.         endContainer.insertData(endOffset, MARK_SELECTION_END);
  121.       else {
  122.         tmpNode = doc.createTextNode(MARK_SELECTION_END);
  123.         endContainer = endContainer.parentNode;
  124.         if (endOffset == 0)
  125.           endContainer.parentNode.insertBefore(tmpNode, endContainer);
  126.         else
  127.           endContainer.parentNode.insertBefore(tmpNode, endContainer.nextSibling);
  128.       }
  129.     }
  130.     else {
  131.       tmpNode = doc.createTextNode(MARK_SELECTION_END);
  132.       endContainer.insertBefore(tmpNode, endContainer.childNodes.item(endOffset));
  133.     }
  134.  
  135.     if (startContainer.nodeType == Node.TEXT_NODE ||
  136.         startContainer.nodeType == Node.CDATA_SECTION_NODE) {
  137.       // do some extra tweaks to try to avoid the view-source output to look like
  138.       // ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection).
  139.       // To get a neat output, the idea here is to remap the start point from:
  140.       // 1. ...<tag>[...   to   ...[<tag>...
  141.       // 2. ...[</tag>...  to   ...</tag>[...
  142.       if ((startOffset > 0 && startOffset < startContainer.data.length) ||
  143.           !startContainer.parentNode || !startContainer.parentNode.parentNode ||
  144.           startContainer != startContainer.parentNode.lastChild)
  145.         startContainer.insertData(startOffset, MARK_SELECTION_START);
  146.       else {
  147.         tmpNode = doc.createTextNode(MARK_SELECTION_START);
  148.         startContainer = startContainer.parentNode;
  149.         if (startOffset == 0)
  150.           startContainer.parentNode.insertBefore(tmpNode, startContainer);
  151.         else
  152.           startContainer.parentNode.insertBefore(tmpNode, startContainer.nextSibling);
  153.       }
  154.     }
  155.     else {
  156.       tmpNode = doc.createTextNode(MARK_SELECTION_START);
  157.       startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset));
  158.     }
  159.   }
  160.  
  161.   // now extract and display the syntax highlighted source
  162.   tmpNode = doc.createElementNS(NS_XHTML, 'div');
  163.   tmpNode.appendChild(ancestorContainer);
  164.  
  165.   // the load is aynchronous and so we will wait until the view-source DOM is done
  166.   // before drawing the selection.
  167.   if (canDrawSelection) {
  168.     window.document.getElementById("appcontent").addEventListener("load", drawSelection, true);
  169.   }
  170.  
  171.   // all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl)
  172.   var loadFlags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
  173.   getBrowser().webNavigation
  174.               .loadURI("view-source:data:text/html;charset=utf-8," + encodeURIComponent(tmpNode.innerHTML),
  175.                        loadFlags, null, null, null);
  176. }
  177.  
  178. ////////////////////////////////////////////////////////////////////////////////
  179. // helper to get a path like FIXptr, but with an array instead of the "tumbler" notation
  180. // see FIXptr: http://lists.w3.org/Archives/Public/www-xml-linking-comments/2001AprJun/att-0074/01-NOTE-FIXptr-20010425.htm
  181. function getPath(ancestor, node)
  182. {
  183.   var n = node;
  184.   var p = n.parentNode;
  185.   if (n == ancestor || !p)
  186.     return null;
  187.   var path = new Array();
  188.   if (!path)
  189.     return null;
  190.   do {
  191.     for (var i = 0; i < p.childNodes.length; i++) {
  192.       if (p.childNodes.item(i) == n) {
  193.         path.push(i);
  194.         break;
  195.       }
  196.     }
  197.     n = p;
  198.     p = n.parentNode;
  199.   } while (n != ancestor && p);
  200.   return path;
  201. }
  202.  
  203. ////////////////////////////////////////////////////////////////////////////////
  204. // using special markers left in the serialized source, this helper makes the
  205. // underlying markup of the selected fragment to automatically appear as selected
  206. // on the inflated view-source DOM
  207. function drawSelection()
  208. {
  209.   getBrowser().contentDocument.title =
  210.     getViewSourceBundle().getString("viewSelectionSourceTitle");
  211.  
  212.   // find the special selection markers that we added earlier, and
  213.   // draw the selection between the two...
  214.   var findService = null;
  215.   try {
  216.     // get the find service which stores the global find state
  217.     findService = Components.classes["@mozilla.org/find/find_service;1"]
  218.                             .getService(Components.interfaces.nsIFindService);
  219.   } catch(e) { }
  220.   if (!findService)
  221.     return;
  222.  
  223.   // cache the current global find state
  224.   var matchCase     = findService.matchCase;
  225.   var entireWord    = findService.entireWord;
  226.   var wrapFind      = findService.wrapFind;
  227.   var findBackwards = findService.findBackwards;
  228.   var searchString  = findService.searchString;
  229.   var replaceString = findService.replaceString;
  230.  
  231.   // setup our find instance
  232.   var findInst = getBrowser().webBrowserFind;
  233.   findInst.matchCase = true;
  234.   findInst.entireWord = false;
  235.   findInst.wrapFind = true;
  236.   findInst.findBackwards = false;
  237.  
  238.   // ...lookup the start mark
  239.   findInst.searchString = MARK_SELECTION_START;
  240.   var startLength = MARK_SELECTION_START.length;
  241.   findInst.findNext();
  242.  
  243.   var contentWindow = getBrowser().contentDocument.defaultView;
  244.   var selection = contentWindow.getSelection();
  245.   var range = selection.getRangeAt(0);
  246.  
  247.   var startContainer = range.startContainer;
  248.   var startOffset = range.startOffset;
  249.  
  250.   // ...lookup the end mark
  251.   findInst.searchString = MARK_SELECTION_END;
  252.   var endLength = MARK_SELECTION_END.length;
  253.   findInst.findNext();
  254.  
  255.   var endContainer = selection.anchorNode;
  256.   var endOffset = selection.anchorOffset;
  257.  
  258.   // reset the selection that find has left
  259.   selection.removeAllRanges();
  260.  
  261.   // delete the special markers now...
  262.   endContainer.deleteData(endOffset, endLength);
  263.   startContainer.deleteData(startOffset, startLength);
  264.   if (startContainer == endContainer)
  265.     endOffset -= startLength; // has shrunk if on same text node...
  266.   range.setEnd(endContainer, endOffset);
  267.  
  268.   // show the selection and scroll it into view
  269.   selection.addRange(range);
  270.   // the default behavior of the selection is to scroll at the end of
  271.   // the selection, whereas in this situation, it is more user-friendly
  272.   // to scroll at the beginning. So we override the default behavior here
  273.   try {
  274.     getBrowser().docShell
  275.                 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
  276.                 .getInterface(Components.interfaces.nsISelectionDisplay)
  277.                 .QueryInterface(Components.interfaces.nsISelectionController)
  278.                 .scrollSelectionIntoView(Components.interfaces.nsISelectionController.SELECTION_NORMAL,
  279.                                          Components.interfaces.nsISelectionController.SELECTION_ANCHOR_REGION,
  280.                                          true);
  281.   }
  282.   catch(e) { }
  283.  
  284.   // restore the current find state
  285.   findService.matchCase     = matchCase;
  286.   findService.entireWord    = entireWord;
  287.   findService.wrapFind      = wrapFind;
  288.   findService.findBackwards = findBackwards;
  289.   findService.searchString  = searchString;
  290.   findService.replaceString = replaceString;
  291.  
  292.   findInst.matchCase     = matchCase;
  293.   findInst.entireWord    = entireWord;
  294.   findInst.wrapFind      = wrapFind;
  295.   findInst.findBackwards = findBackwards;
  296.   findInst.searchString  = searchString;
  297. }
  298.  
  299. ////////////////////////////////////////////////////////////////////////////////
  300. // special handler for markups such as MathML where reformatting the output is
  301. // helpful
  302. function viewPartialSourceForFragment(node, context)
  303. {
  304.   gTargetNode = node;
  305.   if (gTargetNode && gTargetNode.nodeType == Node.TEXT_NODE)
  306.     gTargetNode = gTargetNode.parentNode;
  307.  
  308.   // walk up the tree to the top-level element (e.g., <math>, <svg>)
  309.   var topTag;
  310.   if (context == 'mathml')
  311.     topTag = 'math';
  312.   else
  313.     throw 'not reached';
  314.   var topNode = gTargetNode;
  315.   while (topNode && topNode.localName != topTag)
  316.     topNode = topNode.parentNode;
  317.   if (!topNode)
  318.     return;
  319.  
  320.   // serialize
  321.   var title = getViewSourceBundle().getString("viewMathMLSourceTitle");
  322.   var wrapClass = gWrapLongLines ? ' class="wrap"' : '';
  323.   var source =
  324.     '<html>'
  325.   + '<head><title>' + title + '</title>'
  326.   + '<link rel="stylesheet" type="text/css" href="' + gViewSourceCSS + '">'
  327.   + '<style type="text/css">'
  328.   + '#target { border: dashed 1px; background-color: lightyellow; }'
  329.   + '</style>'
  330.   + '</head>'
  331.   + '<body id="viewsource"' + wrapClass
  332.   +        ' onload="document.title=\''+title+'\';document.getElementById(\'target\').scrollIntoView(true)">'
  333.   + '<pre>'
  334.   + getOuterMarkup(topNode, 0)
  335.   + '</pre></body></html>'
  336.   ; // end
  337.  
  338.   // display
  339.   var doc = getBrowser().contentDocument;
  340.   doc.open("text/html", "replace");
  341.   doc.write(source);
  342.   doc.close();
  343. }
  344.  
  345. ////////////////////////////////////////////////////////////////////////////////
  346. function getInnerMarkup(node, indent) {
  347.   var str = '';
  348.   for (var i = 0; i < node.childNodes.length; i++) {
  349.     str += getOuterMarkup(node.childNodes.item(i), indent);
  350.   }
  351.   return str;
  352. }
  353.  
  354. ////////////////////////////////////////////////////////////////////////////////
  355. function getOuterMarkup(node, indent) {
  356.   var newline = '';
  357.   var padding = '';
  358.   var str = '';
  359.   if (node == gTargetNode) {
  360.     gStartTargetLine = gLineCount;
  361.     str += '</pre><pre id="target">';
  362.   }
  363.  
  364.   switch (node.nodeType) {
  365.   case Node.ELEMENT_NODE: // Element
  366.     // to avoid the wide gap problem, '\n' is not emitted on the first
  367.     // line and the lines before & after the <pre id="target">...</pre>
  368.     if (gLineCount > 0 &&
  369.         gLineCount != gStartTargetLine &&
  370.         gLineCount != gEndTargetLine) {
  371.       newline = '\n';
  372.     }
  373.     gLineCount++;
  374.     if (gDebug) {
  375.       newline += gLineCount;
  376.     }
  377.     for (var k = 0; k < indent; k++) {
  378.       padding += ' ';
  379.     }
  380.     str += newline + padding
  381.         +  '<<span class="start-tag">' + node.nodeName + '</span>';
  382.     for (var i = 0; i < node.attributes.length; i++) {
  383.       var attr = node.attributes.item(i);
  384.       if (!gDebug && attr.nodeName.match(/^[-_]moz/)) {
  385.         continue;
  386.       }
  387.       str += ' <span class="attribute-name">'
  388.           +  attr.nodeName
  389.           +  '</span>=<span class="attribute-value">"'
  390.           +  unicodeTOentity(attr.nodeValue)
  391.           +  '"</span>';
  392.     }
  393.     if (!node.hasChildNodes()) {
  394.       str += '/>';
  395.     }
  396.     else {
  397.       str += '>';
  398.       var oldLine = gLineCount;
  399.       str += getInnerMarkup(node, indent + 2);
  400.       if (oldLine == gLineCount) {
  401.         newline = '';
  402.         padding = '';
  403.       }
  404.       else {
  405.         newline = (gLineCount == gEndTargetLine) ? '' : '\n';
  406.         gLineCount++;
  407.         if (gDebug) {
  408.           newline += gLineCount;
  409.         }
  410.       }
  411.       str += newline + padding
  412.           +  '</<span class="end-tag">' + node.nodeName + '</span>>';
  413.     }
  414.     break;
  415.   case Node.TEXT_NODE: // Text
  416.     var tmp = node.nodeValue;
  417.     tmp = tmp.replace(/(\n|\r|\t)+/g, " ");
  418.     tmp = tmp.replace(/^ +/, "");
  419.     tmp = tmp.replace(/ +$/, "");
  420.     if (tmp.length != 0) {
  421.       str += '<span class="text">' + unicodeTOentity(tmp) + '</span>';
  422.     }
  423.     break;
  424.   default:
  425.     break;
  426.   }
  427.  
  428.   if (node == gTargetNode) {
  429.     gEndTargetLine = gLineCount;
  430.     str += '</pre><pre>';
  431.   }
  432.   return str;
  433. }
  434.  
  435. ////////////////////////////////////////////////////////////////////////////////
  436. function unicodeTOentity(text)
  437. {
  438.   const charTable = {
  439.     '&': '&<span class="entity">amp;</span>',
  440.     '<': '&<span class="entity">lt;</span>',
  441.     '>': '&<span class="entity">gt;</span>',
  442.     '"': '&<span class="entity">quot;</span>'
  443.   };
  444.  
  445.   function charTableLookup(letter) {
  446.     return charTable[letter];
  447.   }
  448.  
  449.   function convertEntity(letter) {
  450.     try {
  451.       var unichar = gEntityConverter.ConvertToEntity(letter, entityVersion);
  452.       var entity = unichar.substring(1); // extract '&'
  453.       return '&<span class="entity">' + entity + '</span>';
  454.     } catch (ex) {
  455.       return letter;
  456.     }
  457.   }
  458.  
  459.   if (!gEntityConverter) {
  460.     try {
  461.       gEntityConverter =
  462.         Components.classes["@mozilla.org/intl/entityconverter;1"]
  463.                   .createInstance(Components.interfaces.nsIEntityConverter);
  464.     } catch(e) { }
  465.   }
  466.  
  467.   const entityVersion = Components.interfaces.nsIEntityConverter.entityW3C;
  468.  
  469.   var str = text;
  470.  
  471.   // replace chars in our charTable
  472.   str = str.replace(/[<>&"]/g, charTableLookup);
  473.  
  474.   // replace chars > 0x7f via nsIEntityConverter
  475.   str = str.replace(/[^\0-\u007f]/g, convertEntity);
  476.  
  477.   return str;
  478. }
  479.